闲话 22.9.28

闲话

今日有多少人切猪国杀呢?我不知道
但我知道的是我反正没敢碰
在这么一个大好的没有模拟赛的时间
为啥要切猪国杀呢

有闲话的构思
但是不觉得能在今天写完
于是今天先瞎扯一点
没准几天之后你们就能看到我想的是啥了
但慢慢等吧(咕咕咕

放假遥遥无期
我推测一下我放假回家能看到cod20发售
我推测一下我放假回家能看到gta6发售
我推测一下我放假回家能听到饭的新歌
我推测一下我放假回家能看到游戏人生12出版
我推测一下我放假回家能看到游戏人生2
我推测一下我放假回家能看到魔禁完结

完了越来越遥遥无期了

upd at 23.4.3
cod20 是 2024 年
gta6 是 2024~25 年
饭的新歌已经出了 是 dinner bell
游戏人生 12 出版了
游戏人生 2 仍然不太可能
魔禁……6


調子はどうですか

空は自由に飛べましたか

すごい魔法が出せましたか

全部夢の中限定品さ

生きてる事ってなんだろな

生きてる人は怖いからな

触れないものを信じるのは

馬鹿のすることと聞きました

……

分治

一类用于解决连续段问题及其进阶问题的小常数做法。由于实现方式的性质,其时间经常是线段树做法的三分之一甚至更小。并且其经常值域无关,这也就给蓄意对std进行卡常然后卡掉线段树提供了借口
而且写起来确实简单

给定一个长为 n 的序列 a。定义 max(l,r)maxi=lr{ai}min(l,r) 同理。在模 109+7 意义下求下式的值:

l=1nr=lnmax(l,r)×min(l,r)

n5×105,ai<109+7.

你当然可以线段树叶子表示左端点,再维护两个单调栈做扫描,每次更新该点作为最大最小值的区间,每个节点在 maxmin 相同时进行计算,并维护一个子区间 maxmin 的和。太naive不展开。

我们现在考虑分治。

分治需要求解落在 [l,r] 区间内的所有子区间。当 l=r 时有答案 al2,作为递归边界出现。
lr 时可以将子区间按是否过当前区间中点 m=l+r2 分为两类。不过中点的可以作为子问题向两侧子区间递归,因此我们在递归函数内只需要处理过中点的区间即可。

我们可以将 [l,m][m+1,r] 分别预处理,讨论不同的最值取值方式。首先预处理 pos[l,m],[pos,m]pos[m+1,r],[m+1,pos] 的最值 minpos,maxpos,随后使用指针扫两侧区间。

接下来的叙述以扫左侧为例。
我们维护一个左侧单减指针 i 表示当前左侧扫到的位置,两个右侧单增指针 pmin,pmax 表示当 i 在特定位置时,比 mini 小的第一个值/比 maxi 大的第一个值出现的位置。i 每次移动 1p 位置随 i 变化而变化。
考虑根据最大值和最小值的位置分别处理区间。

  1. 最值出现在中点同侧。
    这时考虑这部分区间的最大值由 i 侧取得。因此这部分的数量为 (min(pmax,pmin)1)(mid+1)+1,对答案的贡献为 mini×maxi
  2. 最值出现在中点异侧。
    钦定统计时最大值出现在左侧,因此记录最小值在两侧的前缀和 sumpos。只有 pmin<pmax 时该状态才存在,判一下就行。此时不同的右侧最小值之和为 sumpmax1sumpmin1×[pminm+1],乘上 maxi 就是答案。判后面那个的原因是在实现时 summ 存的不是右侧的值,因此直接溢出的得数不是 0

扫右侧的情况直接复制粘贴就好。

但是我们发现一件事,这样会算重。具体地说,我们在判断指针是否移动时需要写条件,比较 minpminimax)的关系。我们应当在两次计算中以不同的方式开闭区间,使他们互补。
具体地说,按照 [l,r],(l,r) 的方式进行两次的计算是可行的,按照 [l,r),(l,r] 的方式也行。但是如果按照 [l,r],[l,r) 或者什么方式确定区间,则显然会重复计算。这部分的调整看代码实现。

记得分治。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
void get(T & x){ /* readIn */ } 
#define rep(i,a,b) for (register int (i) = (a); (i) <= (b); ++(i))
#define pre(i,a,b) for (register int (i) = (a); (i) >= (b); --(i))
const int N = 5e5 + 10, mod = 1e9 + 7;
int n, a[N], ans;

int mx[N], mn[N], sum[N];
void dac (register int l, register int r) {
    if (l == r) { ans = (ans + 1ll * a[l] * a[l]) % mod; return; }
    int mid = l + r >> 1;
    dac(l, mid), dac(mid+1, r);
    sum[mid] = mx[mid] = mn[mid] = a[mid], sum[mid+1] = mx[mid+1] = mn[mid+1] = a[mid+1];
    rep(i,mid+2,r) mn[i] = min(mn[i-1], a[i]), mx[i] = max(mx[i-1], a[i]), sum[i] = (sum[i-1] + mn[i]) % mod;
    pre(i,mid-1,l) mn[i] = min(mn[i+1], a[i]), mx[i] = max(mx[i+1], a[i]), sum[i] = (sum[i+1] + mn[i]) % mod;

    for (register int i = mid, p1 = mid+1, p2 = mid+1; i >= l; -- i) {
        while (p1 <= r and mn[p1] > mn[i]) p1++;
        while (p2 <= r and mx[p2] <= mx[i]) p2++; // (l,r], [l,r) 型的,如果想写 [l,r],(l,r) 的话可以在这里写 > < 在下面写 >= <=
        ans = (ans + 1ll * mn[i] * mx[i] % mod * (min(p1, p2) - mid - 1)) % mod;
        if (p1 < p2 and p2 > mid+1) ans = (ans + 1ll * mx[i] * (sum[p2 - 1] + (p1 != mid+1) * (mod - sum[p1 - 1]))) % mod;    
    }

    for (register int i = mid+1, p1 = mid, p2 = mid; i <= r; ++ i) {
        while (p1 >= l and mn[p1] >= mn[i]) p1--;
        while (p2 >= l and mx[p2] < mx[i]) p2--;
        ans = (ans + 1ll * mn[i] * mx[i] % mod * (mid - max(p1, p2))) % mod;
        if (p1 > p2 and p2 < mid) ans = (ans + 1ll * mx[i] * (sum[p2 + 1] + (p1 != mid) * (mod - sum[p1 + 1]))) % mod;    
    }
}

signed main() {
    get(n);
    rep(i,1,n) get(a[i]); 
    dac(1, n);
    cout << ans << endl;
    return 0;
}

给定一个长为 n 的序列 a。求区间最大值与最小值的popcount值相同的区间个数。

n5×105,ai1018.

这个东西线段树需要做 log(max{ai}) 次。先用单调栈跑出关于每个popcount的操作离线掉,然后再分别做。更改的总次数是 O(n) 的,因为每个位置只会造成一次修改。查询的总次数是 O(nlogn) 的,但是单次查询 O(1) 所以总复杂度还是 O(nlogn) 的。但是常数大的要死,那个 log 里面还有 max{ai}。于是考虑分治。

沿用上面的思路。首先套路地把前后缀最值处理出来。套路
然后分讨。仍然讨论 i 扫左侧的情况。

  1. 最值均出现在左侧。这时若 popcount(maxi)=popcount(mini) 则有贡献。贡献值如上,仍为 min(pmax,pmin)mid1
  2. 钦定最大值出现在左侧。于是我们统计右侧每个位置 min 前缀的popcount,进行一个差分就是答案。

思路较显然。

值得注意的是,分治在该题的时间为线段树的六分之一至七分之一。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
template <typename T> inline void get(T & x){ /* readIn */} 
#define rep(i,a,b) for (register int (i) = (a); (i) <= (b); ++(i))
#define pre(i,a,b) for (register int (i) = (a); (i) >= (b); --(i))
const int N = 1e6 + 10; typedef long long ll;
#define pctl(u) ( __builtin_popcountll(u) )
int n, c[N], buk[64];
ll a[N], ans;

int mx[N], mn[N], buk1[64], buk2[64];
void dac (int l, int r) {
    if (l == r) { ans++; return; }
    int mid = l + r >> 1;
    dac(l, mid), dac(mid+1, r);
    mx[mid] = mn[mid] = mid, mx[mid+1] = mn[mid+1] = mid+1;
    rep(i, mid+2, r) mx[i] = a[i] > a[mx[i-1]] ? i : mx[i-1], mn[i] = a[i] < a[mn[i-1]] ? i : mn[i-1];
    pre(i, mid-1, l) mx[i] = a[i] > a[mx[i+1]] ? i : mx[i+1], mn[i] = a[i] < a[mn[i+1]] ? i : mn[i+1];
    
    memset(buk1, 0, sizeof buk1), memset(buk2, 0, sizeof buk2);
    for (int i = mid+1, p1 = mid, p2 = mid; i <= r; ++i) {
        while (p1 >= l and a[mx[p1]] < a[mx[i]]) 
            buk1[c[mn[p1--]]] ++ ;
        while (p2 >= l and a[mn[p2]] >= a[mn[i]]) 
            buk2[c[mn[p2--]]] ++ ;
        if (c[mx[i]] == c[mn[i]]) ans += mid - max(p1, p2);
        if (p1 < p2) ans += buk1[c[mx[i]]] - buk2[c[mx[i]]];
    }

    memset(buk1, 0, sizeof buk1), memset(buk2, 0, sizeof buk2);
    for (int i = mid, p1 = mid+1, p2 = mid+1; i >= l; --i) {
        while (p1 <= r and a[mx[i]] >= a[mx[p1]]) 
            buk1[c[mn[p1++]]] ++;
        while (p2 <= r and a[mn[i]] < a[mn[p2]]) 
            buk2[c[mn[p2++]]] ++;
        if (c[mx[i]] == c[mn[i]]) ans += min(p1, p2) - mid - 1;
        if (p2 < p1) ans += buk1[c[mx[i]]] - buk2[c[mx[i]]];
    }
}

signed main() {
    get(n);
    rep(i,1,n) get(a[i]), c[i] = pctl(a[i]);
    dac(1, n);
    cout << ans << endl;
    return 0;
}
posted @   joke3579  阅读(99)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示